package service

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"smart-flow/server/config"
	"smart-flow/server/executor"
	"smart-flow/server/goal"
	"smart-flow/server/job"
	"smart-flow/server/logger"
	"smart-flow/server/monitor"
	"smart-flow/server/proto"
	"smart-flow/server/redis"
	"smart-flow/server/region"
	"smart-flow/server/utils"
	"strconv"
	"strings"
	"sync"
	"time"

	redisLib "github.com/go-redis/redis/v8"
	"github.com/google/uuid"
	"go.uber.org/zap"
)

var (
	onlineCountScriptSha  string    //nolint unused
	onlineCountScriptOnce sync.Once //nolint unused
)

type ScanChanData struct {
	Uid    string
	Ip     string
	NeedV6 bool
}

type CheckChanData struct {
	Uid string
	Isp string
}

func EnhanceCount() *proto.CountsResp {
	resp := new(proto.CountsResp)
	resp.CustomCount = monitor.GetOnlineCountByIsp(config.IspCustom)
	resp.CtCount = monitor.GetOnlineCountByIsp(config.IspTelecom)
	resp.CmCount = monitor.GetOnlineCountByIsp(config.IspMobile)
	resp.CuCount = monitor.GetOnlineCountByIsp(config.IspUnicom)
	resp.TotalCount = resp.CustomCount + resp.CtCount + resp.CmCount + resp.CuCount
	var (
		nextCursor uint64
		keys       []string
		err        error
	)
	filter := "江苏省"
	for {
		keys, nextCursor, err = redis.RDB.HScan(context.Background(), config.FlowClientKey, nextCursor, "whaley_*", 1000).Result()
		if err != nil {
			if errors.Is(err, redisLib.Nil) {
				break
			}
			logger.Logger.Error("[业务] Service操作Redis异常.", zap.Error(err))
			break
		}
		var lcClient LcClient
		for i := 0; i < len(keys); i += 2 {
			if len(keys[i]) < 20 {
				continue
			}
			_ = json.Unmarshal([]byte(keys[i+1]), &lcClient)
			if lcClient.Ip == "" {
				resp.UnknownOnlineCount++
				continue
			}
			regionInfo, _ := region.Searcher.SearchByStr(lcClient.Ip)
			if regionInfo == "" {
				resp.UnknownOnlineCount++
				continue
			}
			if strings.Contains(regionInfo, config.IspHumanTelecom) {
				resp.CtOnlineCount++
				if !strings.Contains(regionInfo, filter) {
					resp.CtOnlineFilterCount++
				}
			} else if strings.Contains(regionInfo, config.IspHumanMobile) {
				resp.CmOnlineCount++
				if !strings.Contains(regionInfo, filter) {
					resp.CmOnlineFilterCount++
				}
			} else if strings.Contains(regionInfo, config.IspHumanUnicom) {
				resp.CuOnlineCount++
				if !strings.Contains(regionInfo, filter) {
					resp.CuOnlineFilterCount++
				}
			} else {
				resp.UnknownOnlineCount++
				continue
			}
		}
		if nextCursor == 0 {
			break
		}
	}
	resp.OnlineCount = resp.CtOnlineCount + resp.CmOnlineCount + resp.CuOnlineCount
	resp.OnlineFilterCount = resp.CtOnlineFilterCount + resp.CmOnlineFilterCount + resp.CuOnlineFilterCount
	return resp
}

func FindUidByIp(ip string) *proto.UidResp {
	resp := new(proto.UidResp)
	var (
		nextCursor uint64
		keys       []string
		err        error
	)
	for {
		keys, nextCursor, err = redis.RDB.HScan(context.Background(), config.FlowClientKey, nextCursor, "whaley_*", 1000).Result()
		if err != nil {
			if errors.Is(err, redisLib.Nil) {
				break
			}
			logger.Logger.Error("[业务] Service操作Redis异常.", zap.Error(err))
			break
		}
		var lcClient LcClient
		for i := 0; i < len(keys); i += 2 {
			uid := keys[i]
			if !strings.HasPrefix(uid, "whaley") || len(uid) < 20 {
				continue
			}
			uid = strings.TrimPrefix(keys[i], "whaley_")
			_ = json.Unmarshal([]byte(keys[i+1]), &lcClient)
			if lcClient.Ip == ip {
				resp.Uid = uid
				break
			}
		}
		if nextCursor == 0 {
			break
		}
	}
	return resp
}

func Bandwidth() *proto.BandwidthResp {
	resp := new(proto.BandwidthResp)
	resp.CustomBandwidth = strconv.FormatFloat(float64(monitor.GetDownBandwidthByIsp(config.IspCustom))/1000/1000, 'f', 2, 64)
	resp.CtBandWidth = strconv.FormatFloat(float64(monitor.GetDownBandwidthByIsp(config.IspTelecom))/1000/1000, 'f', 2, 64)
	resp.CmBandWidth = strconv.FormatFloat(float64(monitor.GetDownBandwidthByIsp(config.IspMobile))/1000/1000, 'f', 2, 64)
	resp.CuBandWidth = strconv.FormatFloat(float64(monitor.GetDownBandwidthByIsp(config.IspUnicom))/1000/1000, 'f', 2, 64)
	return resp
}

func TestStart(addr string, duration, rate int, uid string) {
	logger.Logger.Info("[业务] 开始执行沙盒模块", zap.String("uid", uid))
	var scanChanSlice []chan *ScanChanData
	scanChanSlice = append(scanChanSlice, make(chan *ScanChanData, 10))
	go func() {
		defer close(scanChanSlice[0])
		scanChanSlice[0] <- &ScanChanData{
			Uid:    uid,
			Ip:     "",
			NeedV6: false,
		}
	}()
	target := &goal.Target{
		TargetCtx: goal.TargetCtx{
			Isp:        config.IspCustom,
			InfoUrl:    fmt.Sprintf("http://%s:7171/info", addr),
			DownUrl:    fmt.Sprintf("http://%s:7171/download", addr),
			ProveV6Url: fmt.Sprintf("http://%s:7171/info", addr),
		},
	}
	invokeEnhanceForStart(config.IspCustom, target, duration, rate, scanChanSlice)
	logger.Logger.Info("[业务] 沙盒模块执行完成", zap.String("uid", uid))
}

func CustomStart(addr string, limit, duration, rate int) {
	logger.Logger.Info("[业务] 开始执行沙盒模块", zap.Int("duration", duration), zap.Int("rate", rate), zap.Int("limit", limit))
	var scanChanSlice []chan *ScanChanData
	for range config.CustomWorkerCount {
		scanChanSlice = append(scanChanSlice, make(chan *ScanChanData, 10))
	}
	go scanClientForStart(config.IspCustom, "", limit, scanChanSlice, false)
	target := &goal.Target{
		TargetCtx: goal.TargetCtx{
			Isp:        config.IspCustom,
			InfoUrl:    fmt.Sprintf("http://%s:7171/info", addr),
			DownUrl:    fmt.Sprintf("http://%s:7171/download", addr),
			ProveV6Url: fmt.Sprintf("http://%s:7171/info", addr),
		},
	}
	invokeEnhanceForStart(config.IspCustom, target, duration, rate, scanChanSlice)
	logger.Logger.Info("[业务] 沙盒模块执行完成", zap.Int("duration", duration), zap.Int("rate", rate), zap.Int("limit", limit))
}

func Stop(isp string) {
	logger.Logger.Info("[业务] 开始执行停止模块", zap.String("isp", config.GetIspHuman(isp)))
	TaskPause(isp)
	if isp == "" {
		isp = config.IspCustom
	}
	var scanChanSlice []chan *ScanChanData
	for range config.WorkerCount {
		scanChanSlice = append(scanChanSlice, make(chan *ScanChanData, 2000))
	}
	go scanClientForStop(isp, scanChanSlice)
	invokeEnhanceForStop(isp, scanChanSlice)
	TaskResume(isp)
	logger.Logger.Info("[业务] 停止模块执行完成", zap.String("isp", config.GetIspHuman(isp)))
}

func StopOne(uid string) {
	logger.Logger.Info("[业务] 开始执行停止单Agent模块", zap.String("uid", uid))
	var scanChanSlice []chan *ScanChanData
	scanChanSlice = append(scanChanSlice, make(chan *ScanChanData, 10))
	go func() {
		defer close(scanChanSlice[0])
		scanChanSlice[0] <- &ScanChanData{
			Uid:    uid,
			Ip:     "",
			NeedV6: false,
		}
	}()
	invokeEnhanceForStop(config.IspCustom, scanChanSlice)
	logger.Logger.Info("[业务] 停止单Agent模块执行完成", zap.String("uid", uid))
}

func JobProcess(flowJob *job.FlowJob) {
	logger.Logger.Info("[业务] 开始执行任务模块", zap.Any("job", flowJob))
	var duration int
	if flowJob.RunDuration > 0 { // 计算剩余时间
		stopTime := flowJob.StopTime
		remaining := int(stopTime - time.Now().Unix())
		if remaining <= 0 {
			logger.Logger.Warn("[业务] 任务已超过限制时间", zap.Any("job", flowJob))
			return
		} else {
			duration = max(remaining/60, 1)
			if duration > config.TaskAgentDuration*2 { // 剩余时间超过6小时
				duration = 0
			}
		}
	} else if flowJob.RunDuration == -1 {
		duration = 0
	} else {
		return
	}
	targetsV4, targetsV6, limitV4, limitV6 := monitor.ContainersInstance().GetEnableTargets(flowJob)
	if len(targetsV4) == 0 && len(targetsV6) == 0 {
		logger.Logger.Info("[业务] 无可用容器或带宽已达限制", zap.Any("job", flowJob))
		return
	}
	if flowJob.V6Enabled == 1 && limitV6 > 0 {
		JobProcessV6(flowJob, targetsV6, limitV6, duration)
	}
	if flowJob.V4Enabled == 1 && limitV4 > 0 {
		JobProcessV4(flowJob, targetsV4, limitV4, duration)
	}
}

func JobProcessV4(flowJob *job.FlowJob, targets []*goal.Target, limitV4 int, duration int) {
	logger.Logger.Info("[业务] 开始执行任务模块-V4", zap.Any("job", flowJob), zap.Int("limitV4", limitV4), zap.Int("duration", duration))
	var scanChanSliceV4 []chan *ScanChanData
	for range config.WorkerCount {
		scanChanSliceV4 = append(scanChanSliceV4, make(chan *ScanChanData, 2000))
	}
	go scanClientForStart(flowJob.Isp, flowJob.ExIncProvince, limitV4, scanChanSliceV4, false)
	invokeBalanceEnhanceForStart(flowJob.Isp, targets, duration, flowJob.RunRate, scanChanSliceV4)
	logger.Logger.Info("[业务] 任务模块执行完成-V4", zap.Any("job", flowJob), zap.Int("limitV4", limitV4), zap.Int("duration", duration))
}

func JobProcessV6(flowJob *job.FlowJob, targets []*goal.Target, limitV6 int, duration int) {
	logger.Logger.Info("[业务] 开始执行任务模块-V6", zap.Any("job", flowJob), zap.Int("limit", limitV6), zap.Int("duration", duration))
	var scanChanSliceV6 []chan *ScanChanData
	for range config.WorkerCount {
		scanChanSliceV6 = append(scanChanSliceV6, make(chan *ScanChanData, 2000))
	}
	go scanClientForStart(flowJob.Isp, flowJob.ExIncProvince, limitV6, scanChanSliceV6, true)
	invokeBalanceEnhanceForStart(flowJob.Isp, targets, duration, flowJob.RunRate, scanChanSliceV6)
	logger.Logger.Info("[业务] 任务模块执行完成-V6", zap.Any("job", flowJob), zap.Int("limit", limitV6), zap.Int("duration", duration))
}

func TaskList() *proto.TaskListResp {
	taskList := new(proto.TaskListResp)
	result, _ := redis.RDB.HGet(context.Background(), config.FlowTaskKey, config.IspTelecom).Result()
	_ = json.Unmarshal([]byte(result), &taskList.IspCtTask)
	result, _ = redis.RDB.HGet(context.Background(), config.FlowTaskKey, config.IspMobile).Result()
	_ = json.Unmarshal([]byte(result), &taskList.IspCmTask)
	result, _ = redis.RDB.HGet(context.Background(), config.FlowTaskKey, config.IspUnicom).Result()
	_ = json.Unmarshal([]byte(result), &taskList.IspCuTask)
	return taskList
}

func TaskAdd(req *proto.AddTaskReq) bool {
	data, err := redis.RDB.HGet(context.Background(), config.FlowTaskKey, req.Isp).Result()
	if err != nil && !errors.Is(err, redisLib.Nil) {
		logger.Logger.Error("[业务] Service操作Redis异常.", zap.Error(err))
		return false
	}
	if data != "" {
		var flowJob job.FlowJob
		_ = json.Unmarshal([]byte(data), &flowJob)
		if flowJob.Status != config.TaskFinish {
			return false
		}
	}

	flowJob := new(job.FlowJob)
	flowJob.TaskId = uuid.New().String()
	flowJob.Isp = req.Isp
	flowJob.Status = config.TaskInit
	flowJob.ExIncProvince = req.ExIncProvince
	flowJob.RunDuration = req.RunDuration
	flowJob.RunRate = req.RunRate
	flowJob.RunCycle = req.RunCycle
	flowJob.RunTimeHour = req.RunTimeHour
	flowJob.RunTimeMinute = req.RunTimeMinute
	flowJob.RunTimeSecond = req.RunTimeSecond
	flowJob.V4Enabled = req.V4Enabled
	flowJob.V6Enabled = req.V6Enabled
	jobJson, _ := json.Marshal(flowJob)
	if !DispatchTask(req.Isp, jobJson) {
		return false
	}
	redis.RDB.HSet(context.Background(), config.FlowTaskKey, config.IspTelecom, jobJson)
	return true
}

func TaskDel(isp string) {
	PauseTask(isp)
	StopTask(isp)
	redis.RDB.HDel(context.Background(), config.FlowTaskKey, isp)
}

func TaskPause(isp string) {
	PauseTask(isp)
}

func TaskResume(isp string) {
	ResumeTask(isp)
}

func scanClientForStart(isp, province string, limit int, scanChanSlice []chan *ScanChanData, needV6 bool) {
	logger.Logger.Info("[业务] 启动模块开始扫描", zap.Int("limit", limit), zap.String("isp", config.GetIspHuman(isp)))
	defer func() {
		for _, ch := range scanChanSlice {
			close(ch)
		}
	}()
	checkChan := make(chan *CheckChanData, 1000)
	defer close(checkChan)
	go func() {
		for checkData := range checkChan {
			executor.FailureInstance(checkData.Isp).Check(checkData.Uid)
		}
	}()
	var (
		count             int
		nextCursor        uint64
		keys              []string
		err               error
		scanCount         int
		execCount         int
		needV6NotV6Count  int
		needV4NotV6Count  int
		existLoserCount   int
		noRegionCount     int
		filterRegionCount int
	)
	for {
		keys, nextCursor, err = redis.RDB.HScan(context.Background(), config.FlowClientKey, nextCursor, "whaley_*", 1000).Result()
		if err != nil {
			if errors.Is(err, redisLib.Nil) {
				return
			}
			logger.Logger.Error("[业务] Service操作Redis异常.", zap.Error(err))
			return
		}
		var lcClient LcClient
		for i := 0; i < len(keys); i += 2 {
			uid := keys[i]
			if len(uid) < 20 {
				continue
			}
			scanCount++
			uid = strings.TrimPrefix(keys[i], "whaley_")
			if executor.ExecutingInstance(isp).ExistExecutor(uid) {
				execCount++
				continue
			}
			if executor.FailureInstance(isp).ExistLoser(uid) {
				checkChan <- &CheckChanData{
					Isp: isp,
					Uid: uid,
				}
				existLoserCount++
				continue
			}
			if isp == config.IspTelecom { // 电信v4使用不支持v6的Agent
				if needV6 {
					if executor.ExecutingInstance(isp).ExistNotV6Executor(uid) {
						needV6NotV6Count++
						continue
					}
				} else {
					if !executor.ExecutingInstance(isp).ExistNotV6Executor(uid) {
						continue
					}
					needV4NotV6Count++
				}
			}
			if isp != config.IspCustom {
				_ = json.Unmarshal([]byte(keys[i+1]), &lcClient)
				if lcClient.Ip == "" {
					continue
				}
				regionInfo, _ := region.Searcher.SearchByStr(lcClient.Ip)
				if regionInfo == "" {
					noRegionCount++
					logger.Logger.Warn("[业务] ip信息查询失败", zap.String("ip", lcClient.Ip))
					continue
				}
				if !isRightRegion(regionInfo, isp, province) {
					filterRegionCount++
					continue
				}
			}
			hash := utils.APHash(uid) % config.WorkerCount
			count++
			// logger.Logger.Info("[业务] 启动模块扫描到客户端", zap.String("uid", uid), zap.String("ip", lcClient.Ip), zap.Int("count", count), zap.String("isp", config.GetIspHuman(isp)))
			scanChanSlice[hash] <- &ScanChanData{
				Uid:    uid,
				Ip:     lcClient.Ip,
				NeedV6: needV6,
			}
			if limit > 0 && count >= limit {
				logger.Logger.Info("[业务] 启动模块扫描完成",
					zap.Int("count", count),
					zap.Int("limit", limit),
					zap.Int("loserTotalCount", executor.FailureInstance(isp).Count()),
					zap.Int("execCount", execCount),
					zap.Int("needV6NotV6Count", needV6NotV6Count),
					zap.Int("needV4NotV6Count", needV4NotV6Count),
					zap.Int("existLoserCount", existLoserCount),
					zap.Int("noRegionCount", noRegionCount),
					zap.Int("filterRegionCount", filterRegionCount),
					zap.String("isp", config.GetIspHuman(isp)))
				return
			}
		}
		if nextCursor == 0 {
			logger.Logger.Info("[业务] 启动模块扫描所有客户端完成",
				zap.Int("count", count),
				zap.Int("limit", limit),
				zap.Int("loserTotalCount", executor.FailureInstance(isp).Count()),
				zap.Int("scanCount", scanCount),
				zap.Int("execCount", execCount),
				zap.Int("needV6NotV6Count", needV6NotV6Count),
				zap.Int("needV4NotV6Count", needV4NotV6Count),
				zap.Int("existLoserCount", existLoserCount),
				zap.Int("noRegionCount", noRegionCount),
				zap.Int("filterRegionCount", filterRegionCount),
				zap.String("isp", config.GetIspHuman(isp)))
			return
		}
	}
}

func scanClientForStop(isp string, scanChanSlice []chan *ScanChanData) {
	logger.Logger.Info("[业务] 停止模块开始扫描客户端", zap.String("isp", config.GetIspHuman(isp)))
	defer func() {
		for _, ch := range scanChanSlice {
			close(ch)
		}
	}()
	var (
		count      int
		nextCursor uint64
		keys       []string
		err        error
	)
	for {
		keys, nextCursor, err = redis.RDB.HScan(context.Background(), utils.GenIspAnyKey(config.FlowManageKey, isp), nextCursor, "*", 1000).Result()
		if err != nil {
			logger.Logger.Error("[业务] Service操作Redis异常.", zap.Error(err))
			return
		}
		for i := 0; i < len(keys); i += 2 {
			uid := keys[i]
			hash := utils.APHash(uid) % config.WorkerCount
			count++
			// logger.Logger.Info("[业务] 停止模块扫描到客户端", zap.String("uid", uid), zap.Int("count", count), zap.String("isp", config.GetIspHuman(isp)))
			scanChanSlice[hash] <- &ScanChanData{
				Uid:    uid,
				Ip:     "",
				NeedV6: false,
			}
		}
		if nextCursor == 0 {
			logger.Logger.Info("[业务] 停止模块扫描所有客户端完成", zap.Int("count", count), zap.String("isp", config.GetIspHuman(isp)))
			return
		}
	}
}

//nolint:unused
func loadOnlineCountScript(ctx context.Context) {
	var err error
	script := `
		local hash_key = KEYS[1]
		local pattern = 'whaley_*'
		local cursor = '0'
		local count = 0
		repeat
    		local result = redis.call('HSCAN', hash_key, cursor, 'MATCH', pattern)
    		cursor = result[1]
    		local fields = result[2]
    		count = count + (#fields / 2)
		until cursor == '0'
		return count`
	onlineCountScriptSha, err = redis.RDB.ScriptLoad(ctx, script).Result()
	if err != nil {
		logger.Logger.Error("[业务] Lua加载错误", zap.Error(err))
	}
}

//nolint:unused
func getOnlineCount() int64 {
	onlineCountScriptOnce.Do(func() {
		loadOnlineCountScript(context.Background())
	})
	data, err := redis.RDB.EvalSha(context.Background(), onlineCountScriptSha, []string{config.FlowClientKey}).Result()
	if err != nil {
		return 0
	}
	return data.(int64)
}
